package com.nulldev.util.data.Images;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.codecs.uniapi.UniBase64;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Base64.Base64;
import com.nulldev.util.data.Images.third_party.pngencoder.PngEncoder;
import com.nulldev.util.gui.swingAPI.Swing;
import com.nulldev.util.web.URLUtils.URLUtils;

public class ImageUtil {
	private static final boolean USE_NEW_B64_API = Arguments.notArguments("--ImageUtil.useLegacyB64");
	private static final boolean ENABLE_FAST_PNG_ENCODING = Arguments.notArguments("--ImageUtil.disableFastPNG");

	public static Image getImageFromArray(final int[] pixels, final int width, final int height) {
		final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		final WritableRaster raster = (WritableRaster) image.getData();
		raster.setPixels(0, 0, width, height, pixels);
		return image;
	}

	public static BufferedImage createImageFromBytes(final byte[] imageData) {
		final ByteArrayInputStream bais = new ByteArrayInputStream(Variables.requireNonNullObject(imageData, "imageData"));
		try {
			return ImageIO.read(bais);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static ByteBuffer convertImageData(final BufferedImage bi) {
		final ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			if (ENABLE_FAST_PNG_ENCODING) {
				out.write(new PngEncoder().withBufferedImage(bi).withCompressionLevel(9).withTryIndexedEncoding(true).toBytes());
			} else {
				ImageIO.write(bi, "png", out);
			}
			return ByteBuffer.wrap(out.toByteArray());
		} catch (IOException ex) {
			return ByteBuffer.allocate(0);
		}
	}

	public static byte[] convertImageDataBytes(final BufferedImage bi) {
		final ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			if (ENABLE_FAST_PNG_ENCODING) {
				out.write(new PngEncoder().withBufferedImage(bi).withCompressionLevel(9).withTryIndexedEncoding(true).toBytes());
			} else {
				ImageIO.write(bi, "png", out);
			}
			return out.toByteArray();
		} catch (IOException ex) {
			return new byte[0];
		}
	}

	public static String convertImageDataBase64(final BufferedImage bi) {
		final ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			if (ENABLE_FAST_PNG_ENCODING) {
				out.write(new PngEncoder().withBufferedImage(bi).withCompressionLevel(9).withTryIndexedEncoding(true).toBytes());
			} else {
				ImageIO.write(bi, "png", out);
			}
			if (USE_NEW_B64_API) {
				return UniBase64.encodeFromBytes(out.toByteArray());
			} else {
				return Base64.encodeBase64String(out.toByteArray());
			}
		} catch (IOException ex) {
			return null;
		}
	}

	public static BufferedImage createFromColorImg(final Color color, final int w, final int h) {
		return ExecutorManager.globalAsyncSubmit(() -> {
			final BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
			final Graphics2D f = (Graphics2D) img.getGraphics();
			f.setColor(color);
			f.fillRect(0, 0, w, h);
			return img;
		}).waitTillCompletion();
	}

	public static String encodeToString(final BufferedImage image, final String type) {
		return encodeToString(image, type, true);
	}

	public static String encodeToString(final BufferedImage image, final String type, final boolean urlSafe) {
		String imageString = null;
		final ByteArrayOutputStream bos = new ByteArrayOutputStream();

		try {
			if (ENABLE_FAST_PNG_ENCODING && type.equalsIgnoreCase("png")) {
				bos.write(new PngEncoder().withBufferedImage(image).withCompressionLevel(9).withTryIndexedEncoding(true).toBytes());
			} else {
				ImageIO.write(image, type, bos);
			}
			final byte[] imageBytes = bos.toByteArray();

			if (USE_NEW_B64_API) {
				imageString = UniBase64.encodeFromBytes(imageBytes);
			} else {
				final Base64 encoder = new Base64();
				imageString = new String(encoder.encode(imageBytes));
			}

			if (urlSafe) {
				imageString = URLUtils.safeEncode(imageString);
			}

			bos.close();
		} catch (IOException e) {
			JVM.errorln(e);
		}
		return imageString;
	}

	public static Image scale(final Image imageToScale, final int dWidth, final int dHeight) {
		Image scaledImage = null;
		if (imageToScale != null) {
			scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_ARGB);
			final Graphics2D graphics2D = (Graphics2D) scaledImage.getGraphics();
			graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
			graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, Swing.autoAAHint());
			graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
			graphics2D.dispose();
		}
		return scaledImage;
	}

	public static BufferedImage imageToBufferedImage(final Image im) {
		Variables.requireNonNull(im, "im");
		final BufferedImage bi = new BufferedImage(im.getWidth(null), im.getHeight(null), BufferedImage.TYPE_INT_ARGB);
		final Graphics bg = bi.getGraphics();
		bg.drawImage(im, 0, 0, null);
		bg.dispose();
		return bi;
	}

	public static ImageIcon scale(final ImageIcon imageToScale, int dWidth, int dHeight) {
		BufferedImage scaledImage = null;
		if (imageToScale != null) {
			scaledImage = new BufferedImage(dWidth, dHeight, imageToBufferedImage(imageToScale.getImage()).getType());
			final Graphics2D graphics2D = scaledImage.createGraphics();
			graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
			graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, Swing.autoAAHint());
			graphics2D.drawImage(imageToBufferedImage(imageToScale.getImage()), 0, 0, dWidth, dHeight, null);
			graphics2D.dispose();
		}
		return new ImageIcon(scaledImage);
	}
}
